package net.chrisrichardson.ec2deployer.core;

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;

import java.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.*;

import java.text.SimpleDateFormat
import org.apache.commons.logging.*;


class EC2RequestExecutor {

    def Log logger = LogFactory.getLog(getClass())
    private final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
    private AWSProperties awsProperties

    def EC2RequestExecutor(AWSProperties awsProperties) {
        this.awsProperties = awsProperties
    }

    def SimpleDateFormat makeIso8601DateFormat() {
        def iso8601DateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        iso8601DateFormat.setTimeZone(new SimpleTimeZone(0, "GMT")) // Another way??
        return iso8601DateFormat
    }

    def SimpleDateFormat iso8601DateFormat = makeIso8601DateFormat();

    public Node executeRequest(Map parameters) {
        def formattedDate = iso8601DateFormat.format(new Date());

        parameters.putAll(['AWSAccessKeyId': awsProperties.accessKey, 'SignatureVersion': '1', 'Timestamp': formattedDate, 'Version': '2008-05-05'])
        String signature = encryptRequest(parameters, awsProperties.secretKey)
        parameters['Signature'] = URLEncoder.encode(signature)

        String queryString = parameters.collect({it.key + '=' + it.value}).join("&")

        def client = new HttpClient()

        def getMethod = new GetMethod("https://ec2.amazonaws.com/?" + queryString)
        def statusCode = client.executeMethod(getMethod)
        if (statusCode != 200) {
            logger.error(getMethod.getResponseBodyAsString())
            throw new Ec2RequestExecutionException(statusCode, getMethod.getResponseBodyAsString())
        }
        if (!logger.isDebugEnabled()) {
            def responseStream = getMethod.getResponseBodyAsStream()
            def parser = new XmlParser(false, false)
            return parser.parse(responseStream)
        } else {
            String response = getMethod.getResponseBodyAsString()
            logger.debug(response)
            def parser = new XmlParser(false, false)
            return parser.parseText(response)
        }
    }


    public String calculateRFC2104HMAC(String data, String key) {
        try {
            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes("UTF8"), HMAC_SHA1_ALGORITHM);
            Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
            mac.init(signingKey);
            byte[] rawHmac = mac.doFinal(data.getBytes());
            return new String(Base64.encodeBase64(rawHmac));
        }
        catch (Exception e) {
            throw new RuntimeException("Failed to generate HMAC : ", e);
        }
    }

    public String makeDataToHash(Map parameters) {
        parameters.entrySet().sort({a, b -> a.key.toUpperCase() <=> b.key.toUpperCase()}).collect({it.key + URLDecoder.decode(it.value)}).join()
    }

    public String encryptRequest(Map parameters, String secretKey) {
        def data = makeDataToHash(parameters)
        calculateRFC2104HMAC(data, secretKey)
    }

}